/* ----------------------------------------------------------------------------

	pax.widget.datepick.js Copyright (C) 2006, 2008 Mikkel Bergmann, Pointful

	Licence
	-------
	
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

	See lgpl.txt for licence details

---------------------------------------------------------------------------- */


/*
	Script: pax.widget.datepick
		This is a widget that allows you to pick a date, plus parse text such as 'next friday'
		
	Author:
		Mikkel Bergmann, <http://www.pointful.com>
*/

/*
	TODO
	----
	
	. Add time support option; a time selector should appear on the right of the box.
	
		. 	it should be a scrollbox the same height as with widget, and times in 
			1 hr, 1/2 hr or 1/4 hr times (configurable).
			
		.	Also need to update the guesser to allow a time to be entered.

	. Add keyboard support:
	
		arrow keys: to move
		page up / down: month up / down 
		space or enter: select day
		esc: cancel

	. Make it work with Safari

	BUGS
	----

	[done]	. In pax.widget.datePick.init, in FF it is not displaying the icon properly, may have something to do with "display: inline"???
			- Set the width to 200px, that seemed to fix it...
	[done]	. In pax.widget.template, fix rendering issue in IE, where there is a 8px gap on the right side.
	[done]	. In the API examples, the window stretches weirdly...
	[on hold - API to be rewritten, with correct DOCTYPE]	. In the API examples, the heading is at the top displays weirdly...
	
	. The guessing doesn't always observe the dateFormat, I think it may be the way we initialise the guessing.

*/

var pax = pax || {};
pax.widget = pax.widget || {};
pax.widget.datePick = pax.widget.datePick || {
	dateFormat: 'd-m-Y'		//	Default date format (format as defined in pax.date.js)
};


/*	Method: pax.widget.datePick.init
	Displays a button next to the specified field, that can display the calendar. Simply 

	Parameters:
		displayField - the field we want to attach the date picker to
		args - optional argument object to pass to the menu function, ie: {}
		* dateFormat - Select a date format, as specified in <pax.date>, this is the exact format that the standard PHP date libarry uses
		* selectedDate - dd-mm-yyyy formatted date to show, in case there is no date in the displayField, default is today
		* mondayFirst - boolean to choose if monday is first day of the week, default = true
		* dayNum - the day to start on
		* monthNum - the month number to start on (0 = jan, 11 = dec)
		* yearNum - the full year
		* validate - should we validate the date (using the pax validation library)
		* guess - should we guess the date using the pax date library
		* buttonOffset - object with x, y pixel offset for the button, eq: { x: 6, y: 8 }, would offset the button 6px right, 8px down
		
	Example:
		(start code)
			<input id='pax.widget.datePick.init.example1' type='text'>
			[:.
				var example1 = pax.widget.datePick.init( pax.$('pax.widget.datePick.init.example1') );
				exampleCleanup = function() {
					pax.widget.datePick.destroy( pax.$('pax.widget.datePick.init.example1') );
				};
			:]
		(end)
		This example shows a simple date picker, with all the default settings

	Example:
		(start code)
			<input id='pax.widget.datePick.init.example2' type='text' value='07-07-2007'>
			[:.
				pax.widget.datePick.init( pax.$('pax.widget.datePick.init.example2') );
				exampleCleanup = function() {
					pax.widget.datePick.destroy( pax.$('pax.widget.datePick.init.example2') );
				};
			:]
		(end)
		This example shows a simple date picker, with all the default settings, and a pre-populated field

	Example:
		(start code)
			<input id='pax.widget.datePick.init.example3' type='text'>
			[:.
				pax.widget.datePick.init( pax.$('pax.widget.datePick.init.example3'), { selectedDate: '07-07-1988' } );
				exampleCleanup = function() {
					pax.widget.datePick.destroy( pax.$('pax.widget.datePick.init.example3') );
				};
			:]
		(end)
		This example shows a simple date picker, with a specified startDate

*/
pax.widget.datePick.init = function( displayField, args ) {
	//	Add the dateformat to the field
	var dateFormat = pax.widget.datePick.dateFormat;
	if( args )dateFormat = args.dateFormat;
	if( pax.util.getType( args ) == 'string' )dateFormat = args;	//	Assume by default that we're assigning a date format.
	displayField.dateFormat = dateFormat;

	var validate = false;
	if( args && args.validate )validate = args.validate;
	displayField.validate = validate;
	
	var guess = true;
	if( args && ( typeof( args.guess ) != 'undefined' ) )guess = args.guess;
	displayField.guess = guess;
	
	//	The display field MUST float left for us to position the button correctly (as we append the button)
	var style = displayField.style || '';
	style += (style == '')? '': '; ';
	style += 'float: left;';
	displayField.setAttribute( 'style', style );
	displayField.style.styleFloat = 'left';			//	Use styleFloat, this should work in IE
	
	//	Add the button, to display the calendar.
	var myBox = pax.box.append( displayField.id + '_datePickButton', '', 'datePickButton', displayField, 'E' );
	
	//	We give focus to the field, when the box is clicked or mousedown; this is necessary for FF, as the box overlaps the field.
	pax.event.bindEventList( myBox, ['click', 'mouseDown'], function() {
		displayField.focus();
	} );
	
	//	Get button offsets in pixels
	var offsetRight = (args.buttonOffset && args.buttonOffset.x)? args.buttonOffset.x: 0;
	var offsetDown = (args.buttonOffset && args.buttonOffset.y)? args.buttonOffset.y: 0;
	
	//	Adjust left
	var left = myBox.style.left;
	left = ( left.indexOf( 'px' ) > -1 )? left.substring( 0, left.indexOf( 'px' ) ): left;
	myBox.style.left = (parseInt(left - 23) + offsetRight) + 'px';
	pax.css.opacity( myBox, 50 );	// Set opacity at 50%, so we can see text through it.
	
	//	Adjust top
	var top = myBox.style.top;
	top = ( top.indexOf( 'px' ) > -1 )? top.substring( 0, top.indexOf( 'px' ) ): top;
	myBox.style.top = (parseInt(top + 7) + offsetDown) + 'px';
	
	myBox.style.display = "";

	//	Turn the box into a button
	pax.widget.button.init( myBox, {
		baseClass: 'pButton widget datePick',
		showChrome: false,
		width: '16px',
		height: '14px',
		func: function( e ) {
			pax.widget.datePick.display( displayField, args );
			return false;
		},
		inline: false
	} );
	
	//	Initialise the validation and Date guessing function, we want both keyup and blur events
	pax.event.bindEventList( displayField, ['keyup', 'blur'], function(e) {
		e = e || window.event;
		var format = ( displayField.dateFormat )? displayField.dateFormat: 'l F j, Y';
		
		var myDate = Date.guess( displayField.value, format );
		var result = ( myDate )? myDate.dateFormat( format ): 'Invalid date';
		
		//	Show guess box, if we're configured to do so.
		if( displayField.guess ) {
			var dims = pax.util.getPosition( displayField );
			var box = pax.box.show( 'datePickerGuess_' + displayField.id, result, 'pAutoCompResult', dims.x, (dims.y + dims.height) );
			pax.css.opacity( box, 75 );
		}
		
		//	If it's blur, we need to clean up the guess box, and set the date and validity 
		if( e.type == 'blur' ) {
			if( displayField.guess )pax.box.hide( 'datePickerGuess_' + displayField.id );
			pax.widget.datePick.setDateAndValidity( displayField );
		}
	} );
};
	
/*	Private Method: pax.widget.datePick.setDateAndValidity
	Uses the Date object to guess the value of the date in the field, then sets the value, and field validity, provided the validation library is available
	
*/
pax.widget.datePick.setDateAndValidity = function( displayField ) {
	//	Guess the date, allowing for the chosen date format.
	var myDate = Date.guess( displayField.value, displayField.dateFormat );
	var validateField = ( pax.validate && displayField.validate );
	
	if( myDate ) {
		displayField.value = myDate.dateFormat( displayField.dateFormat );
		if( validateField )pax.validate.setValidField( displayField );
	} else {
		if( validateField ) {
			if( displayField.value != '' )pax.validate.setInvalidField( displayField );
			else pax.validate.setValidField( displayField );
		}
	}
};
	
/*	Private Method: pax.widget.datePick.display
	Displays the calendar next to the specified field
*/
pax.widget.datePick.display = function( displayField, args ) {
	args = ( typeof( args ) != 'undefined' )? args: {};
	args.partial = ( args.partial )? args.partial: false;
	var today = new Date();
	var dateFormat = args.dateFormat || pax.widget.datePick.dateFormat;
	//	Get from field value or args value. Assume format is dd-mm-yyyy for now.
	var selectedDate = ( typeof( args.selectedDate ) != 'undefined' )? args.selectedDate : false;
	var myDate = (displayField.guess)? Date.guess( displayField.value, dateFormat ): false;
	//	Note: 'd-m-Y' is the internally used date format for the date picker.
	//	TODO: change this to use a date obj...
	selectedDate = ( myDate )? myDate.dateFormat( 'd-m-Y' ): selectedDate;

	var dayNum = 		( isNaN(args.dayNum) || args.dayNum == null)? today.getDate() : args.dayNum;
	var monthNum = 		( isNaN(args.monthNum) || args.monthNum == null)? today.getMonth() : args.monthNum;
	var yearNum  = 		( isNaN(args.yearNum)  || args.yearNum == null )? today.getFullYear() : args.yearNum;
	var mondayFirst = 	( typeof(args.mondayFirst) != 'undefined' )? args.mondayFirst: true;
	
	//	Extract the starting date, Note - if we don't specify 10 as the base for parseInt, then '08' and '09' will return 0
	if( selectedDate ) {
		var sMonthNum = parseInt( selectedDate.substring( 3, 5 ), 10 ) - 1;
		var sYearNum = 	parseInt( selectedDate.substring( 6, 10 ), 10 );
		dayNum = 		parseInt( selectedDate.substring( 0, 2 ), 10 );
		monthNum = 		( isNaN(args.monthNum) || args.monthNum == null)? sMonthNum: args.monthNum;
		yearNum = 		( isNaN(args.yearNum)  || args.yearNum == null )? sYearNum: args.yearNum;
	}
	
	var firstDay = new Date( yearNum, monthNum, 0 );
	var startingDay = (firstDay.getDay() > 0)? firstDay.getDay() - 1: 6;	//	Note - we assume Monday as the starting week day here - it can be overidden by setting mondayFirst = false.
	var newDate = new Date( yearNum, monthNum );
	var monthLength = newDate.getDaysInMonth();
	var prevDate = ( monthNum == 1 )? new Date( yearNum - 1, 11 ): new Date( yearNum, monthNum - 1 );
	var prevMonthLength = prevDate.getDaysInMonth();
	var monthName = Date.monthNames[monthNum];
	var dayName = Date.dayNamesShort;

	//	Shift the dayNames, if the week starts on a monday (default day is sunday).
	if( mondayFirst ) {
		startingDay = firstDay.getDay();
		dayName = [ dayName[1], dayName[2], dayName[3], dayName[4], dayName[5], dayName[6], dayName[0] ];
	}
	
	//	The model
	var model = pax.defaultArgs( {
		displayField: displayField,		
		dayNum: dayNum,
		monthNum: monthNum,
		monthName: monthName, 
		dateFormat: dateFormat,
		validate: false,
		selectedDate: selectedDate,
		sMonthNum: sMonthNum,
		sYearNum: sYearNum,
		monthLength: monthLength,
		prevMonthLength: prevMonthLength,
		yearNum: yearNum, 
		mondayFirst: mondayFirst,
		startingDay: startingDay,
		dayName: dayName,
		today: { d: today.getDate(), m: today.getMonth(), y: today.getFullYear() },
		datePicker: null,
		datePickerWindow: null,
		
		// Bind IDs post-fixed with '_' + model.target.id
		internalBindIds: {
			prevYear: {
				click: function( e ) {
					pax.widget.datePick.display( datePicker.model.displayField, { 
						monthNum: datePicker.model.monthNum, 
						yearNum: ( (datePicker.model.yearNum > 1)? datePicker.model.yearNum - 1 : 0 ),
						partial: true,
						datePicker: datePicker,
						mondayFirst: datePicker.model.mondayFirst,
						datePickerWindow: datePickerWindow
					} );
				}
			},
			prevMonth: {
				click: function( e ) {
					pax.widget.datePick.display( datePicker.model.displayField, { 
						yearNum: ( datePicker.model.monthNum <= 0 )? datePicker.model.yearNum - 1: datePicker.model.yearNum,
						monthNum: ( (datePicker.model.monthNum >= 1)? datePicker.model.monthNum - 1 : 11 ),
						partial: true,
						datePicker: datePicker,
						mondayFirst: datePicker.model.mondayFirst,
						datePickerWindow: datePickerWindow
					} );
				}
			},
			today: {
				click: function( e ) {
					var today = new Date();
					var day = ( today.getDate() < 10 )? '0' + today.getDate(): today.getDate();
					var month = ( (today.getMonth() + 1) < 10 )? '0' + (today.getMonth() + 1): (today.getMonth() + 1);
					var year = today.getFullYear();
					datePicker.model.displayField.value = ( day + '-' + month + '-' + year );
					pax.widget.datePick.destroy( datePicker );	//	Hide date picker
				}
			},
			nextMonth: {
				click: function( e ) {
					pax.widget.datePick.display( datePicker.model.displayField, { 
						yearNum: ( datePicker.model.monthNum >= 11 )? datePicker.model.yearNum + 1: datePicker.model.yearNum,
						monthNum: ( (datePicker.model.monthNum < 11)? datePicker.model.monthNum + 1 : 0 ),
						partial: true,
						datePicker: datePicker,
						mondayFirst: datePicker.model.mondayFirst,
						datePickerWindow: datePickerWindow
					} );
				}
			},
			nextYear: {
				click: function( e ) {
					pax.widget.datePick.display( datePicker.model.displayField, { 
						monthNum: datePicker.model.monthNum,
						yearNum: datePicker.model.yearNum + 1,
						partial: true,
						datePicker: datePicker,
						mondayFirst: datePicker.model.mondayFirst,
						datePickerWindow: datePickerWindow
					} );
				}
			},
			days: {
				click: function( e ) {
					var clickTarget = e.target || window.event.srcElement;
					var day = clickTarget.innerHTML.replace(/^\s+|\s+$/g,'').replace( '&nbsp;', '');
					if( day.length < 2 )day = '0' + day;
					var year = datePicker.model.yearNum;
					var month = datePicker.model.monthNum;

					// Here we adjust the month and year, the click target is in the previous or next month.
					if( pax.util.hasClassName( clickTarget, 'nextMonth' ) ) {
						month += 1;
						if( month > 11 ) {
							month = 0;
							year += 1;
						}
					}
					if( pax.util.hasClassName( clickTarget, 'prevMonth' ) ) {
						month -= 1;
						if( month < 0 ) {
							month = 11;
							year -= 1;
						}
					}
					
					month = (month >= 9)? (month + 1): '0' + (month + 1);
					datePicker.model.displayField.value = ( day + '-' + month + '-' + year );
					pax.widget.datePick.destroy( datePicker );	// Remove date picker
				},
				mouseover: function( e ) {
					var hoverTarget = e.target || window.event.srcElement;
					if( pax.util.hasClassName( hoverTarget, 'datePickDay' ) ) {
						pax.util.addClassName( hoverTarget, 'hover' );
						function clearCellHover(e) {
							pax.util.removeClassName( hoverTarget, 'hover' );
							pax.event.unbind( hoverTarget, 'mouseout', clearCellHover );
						};
						pax.event.bind( hoverTarget, 'mouseout', clearCellHover );
					}
				}
			}
		}
	}, args );

	/*	For the sake of efficiency, we either show a window and render the widget into it, 
		or simply update the parts we need, such as the date picker and heading.
	*/
	if( ! args.partial ) {	
		//	Attach a window here.
		var datePickerWindow = pax.widget.window.init( displayField.id + '_datePick', {
			width: 180,
			height: 136,
			padding: 0,
			headerAlign: 'right',
			showOverflow: false,
			header: monthName + ' ' + yearNum,
			targetRight: displayField,
			closeButtonClick: function( event, args ) {
				pax.widget.datePick.destroy( datePicker );
			}
		} );
		
		model.datePickerWindow = datePickerWindow;
		
		//	Initialise the widget
		var datePicker = pax.widget.init( {
			model: model,
			template: pax.widget.datePick.template(),
			target: pax.$( datePickerWindow.target.id + '_content' )
		} );

		//	If we're using IE, we MUST update the window, for the iFrame to appear below the window... 
		if( pax.isIe ) {
			var pos = pax.util.getPosition( datePickerWindow.model.target );
			pax.box.setPosition( datePickerWindow.model.target, pos.x, pos.y );
		}
		
		model.datePicker = datePicker;
		
		datePicker.useWidget( datePickerWindow );	// Register widget, so it will be destroyed on cleanup
		datePicker.render();						// Render main date picker

		//	Render the footer
		pax.template.render( pax.widget.datePick.templateFooter(), {
			target: pax.$( datePickerWindow.target.id + '_footer' ),
			value: model
		} );
		
		// Bind specific IDs, (automatically post-fixed with '_' + grid.model.target.id)
		datePicker.internalBindIds( displayField, datePicker.model.internalBindIds );
		
		/*
			Function to hide the date picker, if we mousedown outside it. 
			It MUST be mousedown, and not click, due to IE's event bubbeling order
		*/
		datePicker.bind( document, 'mousedown', function(e) {
			var target = e.target || window.event.srcElement;
			//	Need to iterate on parentNode, till we either hit 'body' or displayField
			if( target ) {
				var hidePicker = true;
				while( target ) {
					target = target.parentNode;
					if( target == datePickerWindow.target || target == pax.$( displayField.id + '_datePickButton' ) )hidePicker = false;
				}
				if( hidePicker )pax.widget.datePick.destroy( datePicker );		//	Hide date picker
			}
		} );
	} else {
		//	We only want to render the days, and set the header
		var datePicker = model.datePicker;
		var myDays = { days: datePicker.model.internalBindIds.days };
		
		datePicker.internalUnbindIds( displayField, myDays );					// Un-bind the days events
		
		pax.template.render( pax.widget.datePick.template(), { 					// Render the days
			value: model,
			partial: 'days_part_' + displayField.id
		} );

		model.datePickerWindow.model.setHeader( monthName + ' ' + yearNum );	// Set the header
		datePicker.model = model;												// Persist the model
		datePicker.internalBindIds( displayField, myDays );						// Bind the days events
	}
	
	return datePicker;
};

/*	Private Method: pax.widget.datePick.destroy
	Kill the date widget
*/
pax.widget.datePick.destroy = function( datePicker ) {
	pax.box.hide( pax.$(datePicker.model.displayField.id + '_datePick') );
	pax.widget.datePick.setDateAndValidity( datePicker.model.displayField );
	datePicker._destroy();
};

/*	Method: pax.widget.datePick.templateFooter
	Returns a PAX template for the buttons on the footer
*/
pax.widget.datePick.templateFooter = function() {
	var datePickerTemplate = "" +
		"<table width='100%'>														" +
		"	<tr>																	" +
		"		<td><div id='prevYear_[:= displayField.id :]'>&nbsp;</div></td>		" +
		"		<td><div id='prevMonth_[:= displayField.id :]'>&nbsp;</div></td>	" +
		"		<td><div id='today_[:= displayField.id :]'>&nbsp;</div></td>		" +
		"		<td><div id='nextMonth_[:= displayField.id :]'>&nbsp;</div></td>	" +
		"		<td><div id='nextYear_[:= displayField.id :]'>&nbsp;</div></td>		" +
		"	</tr>																	" +
		"</table>																	" +
		"[:. " +
		"	datePicker.useWidget(pax.widget.button.init( pax.$( 'prevYear_' + displayField.id ), { " +
		"		baseClass: 'pButtonWindow arrowReverse', " +
		"		showChrome: false, " +
		"		width: '18px', " +
		"		height: '18px' " +
		"	} ));" +
		"	datePicker.useWidget(pax.widget.button.init( pax.$( 'prevMonth_' + displayField.id ), { " +
		"		baseClass: 'pButtonWindow arrowLeft', " +
		"		showChrome: false, " +
		"		width: '18px', " +
		"		height: '18px' " +
		"	} ));" +
		"	datePicker.useWidget(pax.widget.button.init( pax.$( 'today_' + displayField.id ), { content: 'Today' } ));" +
		"	datePicker.useWidget(pax.widget.button.init( pax.$( 'nextMonth_' + displayField.id ), { " +
		"		baseClass: 'pButtonWindow arrowRight', " +
		"		showChrome: false, " +
		"		width: '18px', " +
		"		height: '18px' " +
		"	} ));" +
		"	datePicker.useWidget(pax.widget.button.init( pax.$( 'nextYear_' + displayField.id ), { " +
		"		baseClass: 'pButtonWindow arrowForward', " +
		"		showChrome: false, " +
		"		width: '18px', " +
		"		height: '18px' " +
		"	} )); " +
		":]" +
	"";

	return datePickerTemplate;
};


/*	Method: pax.widget.datePick.template
	Returns a PAX template for the calendar; this template has partial rendering for the days part
	
*/
pax.widget.datePick.template = function() {
	var datePickerTemplate = "" +
		"[:p(days_part_[:= displayField.id :])" +
		"<span id='days_[:= displayField.id :]'>" +
		"	<table class='datePick' cellspacing='0' width='100%'>" +
		"		<tr class='datePickHead'>" +
		"			[: for( var dayCount = 0; dayCount < dayName.length; dayCount++ ) { :]" +
		"			<td class='headDays'>[:= dayName[dayCount] :]</td>	" +
		"			[: } :]" +
		"		</tr>	" +
		"			[:	var day = 1;	" +
		"				for( var weekNum = 0; weekNum < 6; weekNum++ ) {	:]	" +
		"		<tr>	" +
		"			[:		for( var dNum = 0; dNum < 7; dNum++ ) {	:]	" +
		"			[:	if( day <= monthLength && (weekNum > 0 || dNum >= startingDay)) { :]	" +
		"			<td class='datePickDay[: 	if( today.d == day && 	" +
		"											today.m == monthNum &&" +
		"											today.y == yearNum ) { :]	" +
		"												[:= ' today' :]	" +
		"												[: } :]	" +
		"									[: 	if( dayNum == day && 	" +
		"											monthNum == sMonthNum && 	" +
		"											yearNum == sYearNum ) { :]	" +
		"												[:= ' selectedDay' :]	" +
		"												[:  } :]'>" +
		"				[:= (day > 9)? day: '&nbsp;' + day :]	" +
		"			[:		day += 1;	" +
		"				} else { " +
		"					if( day > 1 ) { var pmDay = (pmDay)? pmDay + 1: 1; :]" +
		"			<td class='datePickDay nextMonth'>	" +
		"				[:= '&nbsp;' + pmDay :]	" +
		"				[:	} else { :]	" +
		"			<td class='datePickDay prevMonth'>	" +
		"					[:= prevMonthLength - (startingDay - dNum - 1) :]	" +
		"					[: } :]" +
		"				[: } :]	" +
		"			</td>" +
		"			[:		}  :]" +
		"		</tr>	" +
		"			[:	} :]	" +
		"	</table>	" +
		"</span>" +
		"p:]" +
	"";
	
	return datePickerTemplate;
};
